package org.p2psimulator.entity.overlaynetwork.structures;

import java.util.ArrayList;
import java.util.List;
import org.p2psimulator.entity.Id;
import org.p2psimulator.entity.id.DoubleId;
import org.p2psimulator.entity.overlaynetwork.routingtable.ChordFingerTable;
import org.p2psimulator.exceptions.OverlayNetworkException;
import org.p2psimulator.util.Tuple;

public class ChordRing extends Ring<ChordFingerTable> {

    public ChordRing(int M) {
        super(M);
    }

    /**
     * Insere o nó na posição correta e atualiza
     * os ponteiros de sucessor e predecessor do seu vizinho
     * Deve ser usado durante a fase de load do sistema
     */
    @Override
    public void insertNode(Id<Double> key, ChordFingerTable table) {
        if (!ring.isEmpty()) {
            for (int i = 0; i < ring.size(); i++) {

                if (key.getValue() == ring.get(i).getA().getValue()) {
                    return;
                } else if (key.getValue() < ring.get(i).getA().getValue()) {

                    ChordFingerTable sTable = ring.get(i).getB();
                    sTable.setPredecessor(key);

                    int predIndex = i - 1;
                    if (predIndex < 0) {
                        predIndex = ring.size() - 1;
                    }
                    ChordFingerTable pTable = ring.get(predIndex).getB();
                    pTable.setSucessor(key);

                    table.setSucessor(sTable.getOwner());
                    table.setPredecessor(pTable.getOwner());
                    table.setOwner(key);

                    Tuple<Id<Double>, ChordFingerTable> tuple = new Tuple<Id<Double>, ChordFingerTable>(
                            key, table);
                    ring.add(i, tuple);
                    return;
                }
            }

            /**
             * Se a chave não for menor de pelo menos um nó da rede
             * então é pq ela é a maior de todas e deve ser inserida no final
             */
            table.setSucessor(ring.firstElement().getA());
            table.setPredecessor(ring.lastElement().getA());
            table.setOwner(key);

            ChordFingerTable sTable = ring.firstElement().getB();
            ChordFingerTable pTable = ring.lastElement().getB();

            sTable.setPredecessor(key);
            pTable.setSucessor(key);
            ring.add(new Tuple<Id<Double>, ChordFingerTable>(key, table));
            return;
        } else {

            /**
             * Se a rede estiver vazia, o nó é inserido com seu próprio sucessor e predecessor
             */
            table.setSucessor(key);
            table.setPredecessor(key);
            table.setOwner(key);

            ring.add(new Tuple<Id<Double>, ChordFingerTable>(key, table));
            return;

        }

    }

    /**
     * Insere o nó na posição correta mas não atualiza os ponteiros
     * de sucessor e predecessor dos seu vizinhos, nem o seu.
     * Este processo é deixado para o método de estabilização utilizado.
     * Deve ser usado no decorrer da simulação
     *
     */
    public void insertNodeClean(Id<Double> key, ChordFingerTable table) {

        if (!ring.isEmpty()) {
            for (int i = 0; i < ring.size(); i++) {

                if (key.getValue() == ring.get(i).getA().getValue()) {
                    return;
                } else if (key.getValue() < ring.get(i).getA().getValue()) {

                    table.setOwner(key);

                    Tuple<Id<Double>, ChordFingerTable> tuple = new Tuple<Id<Double>, ChordFingerTable>(
                            key, table);
                    ring.add(i, tuple);
                    return;
                }
            }

            /**
             * Se a chave não for menor de pelo menos um nó da rede
             * então é pq ela é a maior de todas e deve ser inserida no final
             */
            table.setOwner(key);
            ring.add(new Tuple<Id<Double>, ChordFingerTable>(key, table));
            return;
        } else {

            /**
             * Se a rede estiver vazia, o nó é inserido com seu próprio sucessor e predecessor
             */
            table.setSucessor(key);
            table.setPredecessor(key);
            table.setOwner(key);
            ring.add(new Tuple<Id<Double>, ChordFingerTable>(key, table));
            return;

        }

    }

    /**
     * Preenche as demais entradas da finger table de uma dada chave "key" para
     * um espaço de endereçamento m
     *
     * @param key
     * @throws OverlayNetworkException
     */
    public void fillFingers(Id key, int m) throws OverlayNetworkException {

        for (int i = 0; i < m; i++) {

            double n = (Double) key.getValue();

            double entry = (n + Math.pow(2, i)) % Math.pow(2, m);

            DoubleId entryId = new DoubleId(entry);

            Id sucessor = locateSucessor(entryId);

            ChordFingerTable table = (ChordFingerTable) getValue(key);

            if (sucessor != null) {
                if (!table.contains(sucessor)){
                    table.insertNode(sucessor, sucessor);
                }
            }
        }

    }

    public List<Id> findFingers(Id key) {

        List<Id> list = new ArrayList<Id>();
        for (int i = 0; i < maxSize; i++) {

            double n = (Double) key.getValue();

            double entry = (n + Math.pow(2, i)) % Math.pow(2, maxSize);

            DoubleId entryId = new DoubleId(entry);

            Id sucessor = locateSucessor(entryId);

            if (!list.contains(sucessor)){
                list.add(sucessor);
            }
        }
        return list;
    }
}
